package com.yuzhihao.writejvm.attributes;

import com.yuzhihao.writejvm.clazz.ClassInfo;
import com.yuzhihao.writejvm.code.Operation;
import com.yuzhihao.writejvm.constant.ConstantPool;
import com.yuzhihao.writejvm.format.PrintFormat;
import com.yuzhihao.writejvm.read.Parse;
import com.yuzhihao.writejvm.read.Parses;
import com.yuzhihao.writejvm.read.ReadClassFileFatory;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.log4j.Log4j2;
import org.springframework.util.CollectionUtils;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.*;

/**
 * 属性
 * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3">Code</a>
 *
 * @author yzh
 * @since 2021年12月10日16:37:12
 */
@Data
@Log4j2
@EqualsAndHashCode(callSuper = true)
public class Code extends Attributes {

    private int max_stack_u2 = 2;
    private int max_stack;

    private int max_locals_u2 = 2;
    private int max_locals;

    private int code_length_u4 = 4;
    private int code_length;
    private byte[] code;
    private List<Operation> operations = new ArrayList<>();

    private int exception_table_length_u2 = 2;
    private int exception_table_length;
    private List<Exception> exceptions = new ArrayList<>();

    private int attributes_count_u2 = 2;
    private int attributes_count;
    private Map<String,Attributes> cattributes = new TreeMap<>(String::compareTo);

    public Code(String name) {
        super();
        super.setAttribute_name(name);
    }

    @Override
    public void parse(DataInputStream dis, ClassInfo ci) {
        try {
            super.attribute_length = Parse.byteToInt(super.attribute_length_u4, dis);
            this.max_stack = Parse.byteToInt(this.max_stack_u2, dis);
            this.max_locals = Parse.byteToInt(this.max_locals_u2, dis);
            this.code_length = Parse.byteToInt(this.code_length_u4, dis);

            //解析code
            int j = 0;
            while (j < this.code_length){
                int tag = Parse.byteToInt(Operation.tag_u1, dis);
                Operation operation = ReadClassFileFatory.frame_instance(tag);
                if (Objects.nonNull(operation)) {
                    operation.setIndex(j);
                    this.operations.add(operation);
                    if(operation instanceof Parses){
                        Parses parses = (Parses)operation;
                        int number = parses.parse(dis, ci);
                        j += number;
                    }
                }
                j++;
            }

            this.exception_table_length = Parse.byteToInt(this.exception_table_length_u2, dis);
            int i = 0;
            while (i < this.exception_table_length) {
                Exception exception = new Exception();
                exception.parse(dis, ci);
                exceptions.add(exception);
                i++;
            }
            this.attributes_count = Parse.byteToInt(this.attributes_count_u2, dis);
            if (this.attributes_count > 0) {
                Attributes a = new Attributes(this.attributes_count);
                a.parse(dis, ci);
                for (Attributes attribute : a.getAttributes()) {
                    this.cattributes.put(attribute.getAttribute_name(),attribute);
                }
            }
        } catch (IOException e) {
            log.error(e);
        }
    }

    @Override
    public String print(String print) {
        StringBuilder builder = new StringBuilder();
        builder.append(print).append("Code").append(PrintFormat.COLON).append(System.lineSeparator());

        builder.append(print).append(PrintFormat.EMPTY);
        builder.append("stack").append(PrintFormat.EQUAL)
                .append(this.max_stack).append(",").append(PrintFormat.EMPTY);
        builder.append("locals").append(PrintFormat.EQUAL)
                .append(this.max_locals).append(",").append(PrintFormat.EMPTY);
        builder.append("args_size").append(PrintFormat.EQUAL)
                .append(this.argssize()).append(PrintFormat.EMPTY);
        builder.append(System.lineSeparator());

        // code最终解析======
        for (Operation operation : this.operations) {
            builder.append(operation.print(print+PrintFormat.EMPTY));
        }

        //异常表
        if (!CollectionUtils.isEmpty(this.exceptions)) {
            String format = print + PrintFormat.EMPTY;
            builder.append(format).append("Exception table").append(PrintFormat.COLON).append(System.lineSeparator());
            format += PrintFormat.EMPTY;
            String from = "from  ";
            String to = "to  ";
            String target = "target  ";
            String type = "type     ";
            builder.append(format).append(from).append(to).append(target).append(type).append(System.lineSeparator());
            for (Exception exception : this.exceptions) {
                builder.append(format);
                builder.append(stable(String.valueOf(exception.getStart_pc()), from));
                builder.append(stable(String.valueOf(exception.getEnd_pc()), target));
                builder.append(stable(String.valueOf(exception.getHandler_pc()), to));
                builder.append(stable(exception.getType(), type));
                builder.append(System.lineSeparator());
            }
        }

        for (Attributes attribute : this.cattributes.values()) {
            builder.append(attribute.print(print + PrintFormat.EMPTY));
        }

        return builder.toString();
    }

    private int argssize() {
        return 0;
    }


    @Data
    @Log4j2
    public static class Exception implements Parse {
        private int start_pc_u2 = 2;
        private int start_pc;

        private int end_pc_u2 = 2;
        private int end_pc;

        private int handler_pc_u2 = 2;
        private int handler_pc;

        /**
         * If the value of the catch_type item is nonzero, it must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_Class_info structure (§4.4.1) representing a class of exceptions that this exception handler is designated to catch. The exception handler will be called only if the thrown exception is an instance of the given class or one of its subclasses.
         */
        private int catch_type_u2 = 2;
        private int catch_type;

        private String type = "";

        private ClassInfo ci;

        @Override
        public void parse(DataInputStream dis, ClassInfo ci) {
            this.setCi(ci);
            try {
                this.start_pc = Parse.byteToInt(this.start_pc_u2, dis);
                this.end_pc = Parse.byteToInt(this.end_pc_u2, dis);
                this.handler_pc = Parse.byteToInt(this.handler_pc_u2, dis);
                this.catch_type = Parse.byteToInt(this.catch_type_u2, dis);
            } catch (IOException e) {
                log.error(e);
            }
        }

        public String getType() {
            if (!"".equals(this.type)) {
                return this.type;
            }

            if (this.catch_type != 0) {
                ConstantPool cui =  this.getCi().getCp_info().getPools().get(String.valueOf(this.catch_type));
                this.type = "Class" + PrintFormat.EMPTY + cui.getConstant();
            } else {
                this.type = "any";
            }

            return type;
        }
    }

}
